/*
* Copyright (C) 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.errai.ioc.rebind.ioc.bootstrapper;
import static org.jboss.errai.codegen.Parameter.finalOf;
import static org.jboss.errai.codegen.builder.impl.ObjectBuilder.newInstanceOf;
import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs;
import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf;
import static org.jboss.errai.codegen.util.Stmt.castTo;
import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable;
import static org.jboss.errai.codegen.util.Stmt.declareVariable;
import static org.jboss.errai.codegen.util.Stmt.invokeStatic;
import static org.jboss.errai.codegen.util.Stmt.loadLiteral;
import static org.jboss.errai.codegen.util.Stmt.loadVariable;
import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.AbstractBodyGenerator.getAnnotationArrayStmt;
import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.AbstractBodyGenerator.getAssignableTypesArrayStmt;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.NormalScope;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Specializes;
import javax.enterprise.inject.Stereotype;
import javax.enterprise.inject.Typed;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Scope;
import org.jboss.errai.codegen.ArithmeticExpression;
import org.jboss.errai.codegen.ArithmeticOperator;
import org.jboss.errai.codegen.InnerClass;
import org.jboss.errai.codegen.Modifier;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.ContextualStatementBuilder;
import org.jboss.errai.codegen.builder.impl.ArithmeticExpressionBuilder;
import org.jboss.errai.codegen.builder.impl.ClassBuilder;
import org.jboss.errai.codegen.builder.impl.ObjectBuilder;
import org.jboss.errai.codegen.meta.HasAnnotations;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.MetaClassMember;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.meta.MetaParameterizedType;
import org.jboss.errai.codegen.meta.MetaType;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.codegen.util.AnnotationSerializer;
import org.jboss.errai.codegen.util.Bool;
import org.jboss.errai.codegen.util.If;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.config.rebind.EnvUtil;
import org.jboss.errai.config.util.ClassScanner;
import org.jboss.errai.ioc.client.Bootstrapper;
import org.jboss.errai.ioc.client.JsArray;
import org.jboss.errai.ioc.client.WindowInjectionContext;
import org.jboss.errai.ioc.client.WindowInjectionContextStorage;
import org.jboss.errai.ioc.client.api.ContextualTypeProvider;
import org.jboss.errai.ioc.client.api.EnabledByProperty;
import org.jboss.errai.ioc.client.api.EntryPoint;
import org.jboss.errai.ioc.client.api.IOCProvider;
import org.jboss.errai.ioc.client.api.LoadAsync;
import org.jboss.errai.ioc.client.api.ScopeContext;
import org.jboss.errai.ioc.client.api.SharedSingleton;
import org.jboss.errai.ioc.client.container.Context;
import org.jboss.errai.ioc.client.container.ContextManager;
import org.jboss.errai.ioc.client.container.ContextManagerImpl;
import org.jboss.errai.ioc.client.container.DependentScopeContext;
import org.jboss.errai.ioc.client.container.Factory;
import org.jboss.errai.ioc.client.container.FactoryHandleImpl;
import org.jboss.errai.ioc.client.container.IOC;
import org.jboss.errai.ioc.client.container.JsTypeProvider;
import org.jboss.errai.ioc.client.container.async.AsyncBeanManagerSetup;
import org.jboss.errai.ioc.client.container.async.AsyncBeanManagerSetup.FactoryLoader;
import org.jboss.errai.ioc.client.container.async.AsyncBeanManagerSetup.FactoryLoaderCallback;
import org.jboss.errai.ioc.client.container.async.DefaultRunAsyncCallback;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraph;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.Dependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.InjectableType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.ReachabilityStrategy;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Qualifier;
import org.jboss.errai.ioc.rebind.ioc.graph.api.QualifierFactory;
import org.jboss.errai.ioc.rebind.ioc.graph.impl.DependencyGraphBuilderImpl;
import org.jboss.errai.ioc.rebind.ioc.graph.impl.InjectableHandle;
import org.jboss.errai.ioc.rebind.ioc.injector.api.ExtensionTypeCallback;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectableProvider;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext;
import org.jboss.errai.ioc.rebind.ioc.injector.api.WiringElementType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import jsinterop.annotations.JsType;
/**
* Creates {@link DependencyGraph} by adding all types and dependencies to the
* {@link DependencyGraphBuilder}. Generates {@link Factory} subclasses and
* {@link GWT#create(Class)} calls for every {@link Injectable} in the
* dependency graph.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class IOCProcessor {
private static final Logger log = LoggerFactory.getLogger(IOCProcessor.class);
public static final Predicate<List<InjectableHandle>> ANY = handle -> true;
public static final Predicate<List<InjectableHandle>> EXACT_TYPE = IOCProcessor::exactTypePredicate;
public static final String REACHABILITY_PROPERTY = "errai.ioc.reachability";
public static final String PLUGIN_PROPERTY = "errai.ioc.jsinterop.support";
public static boolean isJsInteropSupportEnabled() {
return Boolean.getBoolean(PLUGIN_PROPERTY);
}
private final Set<Class<? extends Annotation>> nonSimpletonTypeAnnotations = new HashSet<>();
private final InjectionContext injectionContext;
private final QualifierFactory qualFactory;
private Collection<String> alternatives;
public IOCProcessor(final InjectionContext injectionContext) {
this.injectionContext = injectionContext;
this.qualFactory = injectionContext.getQualifierFactory();
nonSimpletonTypeAnnotations.add(IOCProvider.class);
nonSimpletonTypeAnnotations.add(Specializes.class);
nonSimpletonTypeAnnotations.add(LoadAsync.class);
nonSimpletonTypeAnnotations.add(EnabledByProperty.class);
nonSimpletonTypeAnnotations.add(Typed.class);
nonSimpletonTypeAnnotations.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.DependentBean));
nonSimpletonTypeAnnotations.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.PseudoScopedBean));
nonSimpletonTypeAnnotations.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.NormalScopedBean));
nonSimpletonTypeAnnotations.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.AlternativeBean));
}
/**
* Process the dependency graph and generate code in BoostrapperImpl to declare and create factories for all {@link Injectable injectables}.
*
* @param processingContext
*/
public void process(final IOCProcessingContext processingContext) {
long start = System.currentTimeMillis();
final Collection<MetaClass> allMetaClasses = findRelevantClasses(processingContext);
log.debug("Found {} classes", allMetaClasses.size());
final DependencyGraphBuilder graphBuilder = new DependencyGraphBuilderImpl(qualFactory, injectionContext.isAsync());
runExtensionCallbacks(allMetaClasses);
log.debug("Ran {} extension callbacks on all types {} types.", injectionContext.getExtensionTypeCallbacks().size(), allMetaClasses.size());
addAllInjectableProviders(graphBuilder);
processDependencies(allMetaClasses, graphBuilder);
log.debug("Added {} classes to dependency graph in {}ms", allMetaClasses.size(), System.currentTimeMillis() - start);
start = System.currentTimeMillis();
final DependencyGraph dependencyGraph = graphBuilder.createGraph(getReachabilityStrategy());
log.debug("Resolved dependency graph with {} reachable injectables in {}ms", dependencyGraph.getNumberOfInjectables(), System.currentTimeMillis() - start);
FactoryGenerator.resetTotalTime();
FactoryGenerator.setDependencyGraph(dependencyGraph);
FactoryGenerator.setInjectionContext(injectionContext);
start = System.currentTimeMillis();
final Map<Class<? extends Annotation>, MetaClass> scopeContexts = findScopeContexts(processingContext);
final Set<MetaClass> scopeContextSet = new LinkedHashSet<>(scopeContexts.values());
final Statement[] contextLocalVarInvocation = contextLocalVarInvocation(scopeContextSet);
@SuppressWarnings("rawtypes")
final BlockBuilder registerFactoriesBody = createRegisterFactoriesMethod(processingContext, scopeContextSet);
declareAndRegisterFactories(processingContext, dependencyGraph, scopeContexts, scopeContextSet, registerFactoriesBody);
final String contextManagerFieldName = declareContextManagerField(processingContext);
if (isJsInteropSupportEnabled()) {
declareWindowInjectionContextField(processingContext);
}
declareStaticLogger(processingContext);
if (injectionContext.isAsync()) {
declareAsyncBeanManagerSetupField(processingContext);
}
registerFactoriesBody.finish();
bootstrapContainer(processingContext, dependencyGraph, scopeContextSet, contextLocalVarInvocation, contextManagerFieldName);
log.debug("Processed factory GWT.create calls in {}ms", System.currentTimeMillis() - start);
}
private ReachabilityStrategy getReachabilityStrategy() {
final String reachabilityStrategyName = System.getProperty(REACHABILITY_PROPERTY, ReachabilityStrategy.Annotated.name());
log.info("Reachability strategy set to " + reachabilityStrategyName);
try {
return ReachabilityStrategy.valueOf(reachabilityStrategyName);
} catch (final IllegalArgumentException iae) {
throw new RuntimeException("Unrecognized reachability strategy, " + reachabilityStrategyName
+ ". Please use one of the following: " + Arrays.toString(ReachabilityStrategy.values()), iae);
}
}
private void bootstrapContainer(final IOCProcessingContext processingContext, final DependencyGraph dependencyGraph,
final Set<MetaClass> scopeContextSet, final Statement[] contextLocalVarInvocation,
final String contextManagerFieldName) {
processingContext.getBlockBuilder()
.appendAll(contextLocalVarDeclarations(scopeContextSet))
.append(loadVariable("logger").invoke("debug", "Registering factories with contexts."))
.append(declareVariable("start", long.class, currentTime()))
.append(loadVariable("this").invoke("registerFactories", (Object[]) contextLocalVarInvocation))
.append(loadVariable("logger").invoke("debug",
"Registered " + dependencyGraph.getNumberOfInjectables() + " factories in {}ms", subtractFromCurrentTime(loadVariable("start"))))
.append(loadVariable("logger").invoke("debug", "Adding contexts to context manager..."))
.append(loadVariable("start").assignValue(currentTime()));
addContextsToContextManager(scopeContextSet, contextManagerFieldName, processingContext.getBlockBuilder());
processingContext.getBlockBuilder()
.append(loadVariable("logger").invoke("debug",
"Added " + scopeContextSet.size() + " contexts in {}ms", subtractFromCurrentTime(loadVariable("start"))))
.append(loadVariable("logger").invoke("debug", "Calling finishInit on " + ContextManager.class.getSimpleName()))
.append(loadVariable("start").assignValue(currentTime()));
callFinishInitOnContextManager(contextManagerFieldName, processingContext.getBlockBuilder());
processingContext.getBlockBuilder()
.append(loadVariable("logger").invoke("debug",
"ContextManager#finishInit ran in {}ms", subtractFromCurrentTime(loadVariable("start"))));
}
private static ContextualStatementBuilder currentTime() {
return invokeStatic(System.class, "currentTimeMillis");
}
private static ArithmeticExpression subtractFromCurrentTime(final Statement toSubtract) {
return ArithmeticExpressionBuilder.create(currentTime(),
ArithmeticOperator.Subtraction, toSubtract);
}
private void runExtensionCallbacks(final Collection<MetaClass> allMetaClasses) {
final Collection<ExtensionTypeCallback> extensionCallbacks = injectionContext.getExtensionTypeCallbacks();
extensionCallbacks.forEach(cb -> cb.init());
allMetaClasses.forEach(mc -> extensionCallbacks.forEach(cb -> cb.callback(mc)));
extensionCallbacks.forEach(cb -> cb.finish());
}
private void callFinishInitOnContextManager(final String contextManagerFieldName, final BlockBuilder<?> methodBody) {
methodBody.append(loadVariable(contextManagerFieldName).invoke("finishInit"));
}
private void addAllInjectableProviders(final DependencyGraphBuilder graphBuilder) {
for (final Entry<InjectableHandle, InjectableProvider> entry : injectionContext.getInjectableProviders().entries()) {
graphBuilder.addExtensionInjectable(entry.getKey().getType(), entry.getKey().getQualifier(), ANY, entry.getValue());
}
for (final Entry<InjectableHandle, InjectableProvider> entry : injectionContext.getExactTypeInjectableProviders().entries()) {
graphBuilder.addExtensionInjectable(entry.getKey().getType(), entry.getKey().getQualifier(), EXACT_TYPE, entry.getValue());
}
}
private void addContextsToContextManager(final Collection<MetaClass> scopeContextImpls,
final String contextManagerFieldName, @SuppressWarnings("rawtypes") final BlockBuilder methodBody) {
for (final MetaClass scopeContextImpl : scopeContextImpls) {
methodBody.append(loadVariable(contextManagerFieldName).invoke("addContext", loadVariable(getContextVarName(scopeContextImpl))));
}
}
@SuppressWarnings("unchecked")
private String declareContextManagerField(final IOCProcessingContext processingContext) {
final String contextManagerFieldName = "contextManager";
processingContext.getBootstrapBuilder()
.privateField(contextManagerFieldName, ContextManager.class)
.initializesWith(ObjectBuilder.newInstanceOf(ContextManagerImpl.class))
.finish();
return contextManagerFieldName;
}
@SuppressWarnings("unchecked")
private void declareStaticLogger(final IOCProcessingContext processingContext) {
processingContext.getBootstrapBuilder()
.privateField("logger", Logger.class)
.modifiers(Modifier.Static, Modifier.Final)
.initializesWith(invokeStatic(LoggerFactory.class, "getLogger", Bootstrapper.class))
.finish();
}
@SuppressWarnings("unchecked")
private void declareAsyncBeanManagerSetupField(final IOCProcessingContext processingContext) {
processingContext.getBootstrapBuilder()
.privateField("asyncBeanManagerSetup", AsyncBeanManagerSetup.class)
.initializesWith(castTo(AsyncBeanManagerSetup.class, invokeStatic(IOC.class, "getAsyncBeanManager")))
.finish();
}
@SuppressWarnings("unchecked")
private void declareWindowInjectionContextField(final IOCProcessingContext processingContext) {
processingContext.getBootstrapBuilder().privateField("windowContext", WindowInjectionContext.class)
.modifiers(Modifier.Final).initializesWith(Stmt.invokeStatic(WindowInjectionContextStorage.class, "createOrGet"))
.finish();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void declareAndRegisterFactories(final IOCProcessingContext processingContext,
final DependencyGraph dependencyGraph, final Map<Class<? extends Annotation>, MetaClass> scopeContexts,
final Collection<MetaClass> orderedScopeContexts, final BlockBuilder registerFactoriesBody) {
final Parameter[] contextParamsDeclaration = contextParamsDeclaration(orderedScopeContexts);
final Statement[] contextLocalVarInvocation = contextLocalVarInvocation(orderedScopeContexts);
int methodNumber = 0;
int registeredInThisMethod = 0;
BlockBuilder curMethod = null;
for (final Injectable injectable : dependencyGraph) {
if (registeredInThisMethod % 500 == 0) {
if (curMethod != null) {
curMethod.finish();
registerFactoriesBody.append(loadVariable("this").invoke("registerFactories" + methodNumber, (Object[]) contextLocalVarInvocation));
methodNumber++;
registeredInThisMethod = 0;
}
curMethod = processingContext.getBootstrapBuilder().privateMethod(void.class, "registerFactories" + methodNumber, contextParamsDeclaration).body();
}
declareAndProcessInjectable(processingContext, scopeContexts, curMethod, injectable);
registeredInThisMethod++;
}
if (curMethod != null) {
curMethod.finish();
registerFactoriesBody.append(loadVariable("this").invoke("registerFactories" + methodNumber, (Object[]) contextLocalVarInvocation));
}
}
private void declareAndProcessInjectable(final IOCProcessingContext processingContext,
final Map<Class<? extends Annotation>, MetaClass> scopeContexts,
@SuppressWarnings("rawtypes") final BlockBuilder curMethod, final Injectable injectable) {
if (injectionContext.isAsync() && injectable.loadAsync()) {
final MetaClass factoryClass = addFactoryDeclaration(injectable, processingContext);
registerAsyncFactory(injectable, processingContext, curMethod, factoryClass);
} else {
declareAndRegisterConcreteInjectable(injectable, processingContext, scopeContexts, curMethod);
}
}
private void registerAsyncFactory(final Injectable injectable, final IOCProcessingContext processingContext,
@SuppressWarnings("rawtypes") final BlockBuilder curMethod, final MetaClass factoryClass) {
final Statement handle = generateFactoryHandle(injectable, curMethod);
final Statement loader = generateFactoryLoader(injectable, factoryClass);
curMethod.append(loadVariable("asyncBeanManagerSetup").invoke("registerAsyncBean", handle, loader));
for (final Dependency dep : injectable.getDependencies()) {
if (dep.getInjectable().loadAsync()) {
curMethod.append(loadVariable("asyncBeanManagerSetup").invoke("registerAsyncDependency", injectable.getFactoryName(),
dep.getInjectable().getFactoryName()));
}
}
}
private Statement generateFactoryLoader(final Injectable injectable, final MetaClass factoryClass) {
final Statement runAsyncCallback = ObjectBuilder.newInstanceOf(DefaultRunAsyncCallback.class).extend()
.publicOverridesMethod("onSuccess").append(loadVariable("callback").invoke("callback",
castTo(Factory.class, invokeStatic(GWT.class, "create", loadLiteral(factoryClass)))))
.finish().finish();
final Class<?> fragmentId = getAsyncFragmentId(injectable);
final Object[] runAsyncParams = (fragmentId.equals(LoadAsync.NO_FRAGMENT.class) ? new Object[] { runAsyncCallback }
: new Object[] { fragmentId, runAsyncCallback });
return ObjectBuilder.newInstanceOf(FactoryLoader.class).extend()
.publicOverridesMethod("call", finalOf(FactoryLoaderCallback.class, "callback"))
.append(invokeStatic(GWT.class, "runAsync", runAsyncParams)).finish().finish();
}
private Class<?> getAsyncFragmentId(final Injectable injectable) {
final LoadAsync loadAsync = injectable.getInjectedType().getAnnotation(LoadAsync.class);
if (loadAsync == null) {
return LoadAsync.NO_FRAGMENT.class;
} else {
return loadAsync.value();
}
}
private Statement generateFactoryHandle(final Injectable injectable,
@SuppressWarnings("rawtypes") final BlockBuilder curMethod) {
final String handleVarName = "handleFor" + injectable.getFactoryName();
curMethod.append(declareFinalVariable(handleVarName, FactoryHandleImpl.class, ObjectBuilder.newInstanceOf(FactoryHandleImpl.class)
.withParameters(loadLiteral(injectable.getInjectedType()),
loadLiteral(injectable.getFactoryName()),
loadLiteral(injectable.getScope()),
loadLiteral(false),
loadLiteral(injectable.getBeanName()),
loadLiteral(!injectable.isContextual()))));
curMethod.append(loadVariable(handleVarName).invoke("setAssignableTypes", getAssignableTypesArrayStmt(injectable)));
if (!injectable.getQualifier().isDefaultQualifier()) {
curMethod.append(loadVariable(handleVarName).invoke("setQualifiers", getAnnotationArrayStmt(injectable.getQualifier())));
}
return loadVariable(handleVarName);
}
@SuppressWarnings("unchecked")
private void declareAndRegisterConcreteInjectable(final Injectable injectable,
final IOCProcessingContext processingContext, final Map<Class<? extends Annotation>, MetaClass> scopeContexts,
@SuppressWarnings("rawtypes") final BlockBuilder registerFactoriesBody) {
final MetaClass factoryClass = addFactoryDeclaration(injectable, processingContext);
registerFactoryWithContext(injectable, factoryClass, scopeContexts, registerFactoriesBody);
final boolean windowScoped = injectable.getWiringElementTypes().contains(WiringElementType.SharedSingleton);
final boolean jsType = injectable.getWiringElementTypes().contains(WiringElementType.JsType);
final boolean jsinteropSupportEnabled = isJsInteropSupportEnabled();
if (jsinteropSupportEnabled && (jsType || windowScoped)) {
final List<Statement> stmts = new ArrayList<>();
stmts.add(loadVariable("windowContext").invoke("addBeanProvider",
injectable.getInjectedType().getFullyQualifiedName(), createJsTypeProviderFor(injectable)));
for (final MetaClass mc : injectable.getInjectedType().getAllSuperTypesAndInterfaces()) {
if (mc.isPublic() && !mc.equals(injectable.getInjectedType())
&& !mc.getFullyQualifiedName().equals("java.lang.Object") && mc.isAnnotationPresent(JsType.class)) {
stmts.add(loadVariable("windowContext").invoke("addSuperTypeAlias",
mc.getFullyQualifiedName(), injectable.getInjectedType().getFullyQualifiedName()));
}
}
if (windowScoped) {
registerFactoriesBody
.append(If
.cond(Bool.expr(loadVariable("windowContext").invoke("hasProvider",
injectable.getInjectedType().getFullyQualifiedName())).negate())
.appendAll(stmts).finish());
}
else {
registerFactoriesBody.appendAll(stmts);
}
}
}
private Statement createJsTypeProviderFor(final Injectable injectable) {
final MetaClass type = injectable.getInjectedType();
final AnonymousClassStructureBuilder jsTypeProvider = newInstanceOf(parameterizedAs(JsTypeProvider.class, typeParametersOf(type))).extend();
jsTypeProvider
.publicOverridesMethod("getInstance")
.append(Stmt.castTo(type, loadVariable("contextManager").invoke("getInstance", injectable.getFactoryName()))
.returnValue()).finish()
.publicOverridesMethod("getName")
.append(Stmt.loadLiteral(getBeanName(injectable)).returnValue())
.finish()
.publicOverridesMethod("getFactoryName")
.append(Stmt.loadLiteral(injectable.getFactoryName()).returnValue())
.finish()
.publicOverridesMethod("getQualifiers")
.append(Stmt.nestedCall(Stmt.newObject(parameterizedAs(JsArray.class, typeParametersOf(String.class)),
Stmt.loadLiteral(AnnotationSerializer.serialize(injectable.getQualifier().iterator())))).returnValue())
.finish();
return jsTypeProvider.finish();
}
private String getBeanName(final Injectable injectable) {
final Named named = injectable.getInjectedType().getAnnotation(Named.class);
return (named != null) ? named.value() : null;
}
@SuppressWarnings("rawtypes")
private BlockBuilder createRegisterFactoriesMethod(final IOCProcessingContext processingContext,
final Collection<MetaClass> scopeContexts) {
final Parameter[] contextParams = contextParamsDeclaration(scopeContexts);
@SuppressWarnings({ "unchecked" })
final BlockBuilder methodBody = processingContext.getBootstrapBuilder().privateMethod(void.class, "registerFactories", contextParams).body();
return methodBody;
}
private Parameter[] contextParamsDeclaration(final Collection<MetaClass> scopeContexts) {
final Parameter[] contextParams = new Parameter[scopeContexts.size()];
final Iterator<MetaClass> iter = scopeContexts.iterator();
int i = 0;
while (iter.hasNext()) {
final MetaClass scopeContextImpl = iter.next();
contextParams[i++] = finalOf(Context.class, getContextVarName(scopeContextImpl));
}
return contextParams;
}
private Statement[] contextLocalVarInvocation(final Collection<MetaClass> scopeContexts) {
final Statement[] vars = new Statement[scopeContexts.size()];
final Iterator<MetaClass> iter = scopeContexts.iterator();
int i = 0;
while (iter.hasNext()) {
vars[i++] = loadVariable(getContextVarName(iter.next()));
}
return vars;
}
private List<Statement> contextLocalVarDeclarations(final Collection<MetaClass> scopeContextTypes) {
final List<Statement> declarations = new ArrayList<>();
for (final MetaClass scopeContextImpl : scopeContextTypes) {
if (!scopeContextImpl.isDefaultInstantiable()) {
throw new RuntimeException(
"The @ScopeContext " + scopeContextImpl.getName() + " must have a public, no-args constructor.");
}
declarations.add(declareFinalVariable(getContextVarName(scopeContextImpl), Context.class,
newInstanceOf(scopeContextImpl)));
}
return declarations;
}
private void registerFactoryWithContext(final Injectable injectable, final MetaClass factoryClass,
final Map<Class<? extends Annotation>, MetaClass> scopeContexts,
@SuppressWarnings("rawtypes") final BlockBuilder registerFactoriesBody) {
final Class<? extends Annotation> scope = injectable.getScope();
final MetaClass injectedType = injectable.getInjectedType();
final MetaClass scopeContextImpl = Assert.notNull("No scope context for " + scope.getSimpleName(), scopeContexts.get(scope));
final String contextVarName = getContextVarName(scopeContextImpl);
registerFactoriesBody.append(loadVariable(contextVarName).invoke("registerFactory",
Stmt.castTo(parameterizedAs(Factory.class, typeParametersOf(injectedType)),
invokeStatic(GWT.class, "create", factoryClass))));
}
private String getContextVarName(final MetaClass scopeContextImpl) {
return scopeContextImpl.getFullyQualifiedName().replace('.', '_') + "_context";
}
private Map<Class<? extends Annotation>, MetaClass> findScopeContexts(final IOCProcessingContext processingContext) {
final Collection<MetaClass> scopeContexts = ClassScanner.getTypesAnnotatedWith(ScopeContext.class);
final Map<Class<? extends Annotation>, MetaClass> annoToContextImpl = new HashMap<>();
for (final MetaClass scopeContext : scopeContexts) {
if (!scopeContext.isAssignableTo(Context.class)) {
throw new RuntimeException("They type " + scopeContext.getFullyQualifiedName()
+ " was annotated with @ScopeContext but does not implement " + Context.class.getName());
}
final ScopeContext anno = scopeContext.getAnnotation(ScopeContext.class);
for (final Class<? extends Annotation> scope : anno.value()) {
annoToContextImpl.put(scope, scopeContext);
}
}
final MetaClass depContextImpl = MetaClassFactory.get(DependentScopeContext.class);
for (final Class<? extends Annotation> customAnno : injectionContext.getAnnotationsForElementType(WiringElementType.DependentBean)) {
annoToContextImpl.put(customAnno, depContextImpl);
}
return annoToContextImpl;
}
private Collection<MetaClass> findRelevantClasses(final IOCProcessingContext processingContext) {
final Collection<MetaClass> allMetaClasses = new HashSet<>();
allMetaClasses.addAll(MetaClassFactory.getAllCachedClasses());
allMetaClasses.remove(MetaClassFactory.get(Object.class));
return allMetaClasses;
}
private MetaClass addFactoryDeclaration(final Injectable injectable, final IOCProcessingContext processingContext) {
final String factoryName = injectable.getFactoryName();
final MetaClass typeCreatedByFactory = injectable.getInjectedType();
return addFactoryDeclaration(factoryName, typeCreatedByFactory, processingContext);
}
private MetaClass addFactoryDeclaration(final String factoryName, final MetaClass typeCreatedByFactory,
final IOCProcessingContext processingContext) {
final ClassStructureBuilder<?> builder = processingContext.getBootstrapBuilder();
final BuildMetaClass factory = ClassBuilder
.define(factoryName,
parameterizedAs(Factory.class, typeParametersOf(typeCreatedByFactory)))
.publicScope().abstractClass().body().getClassDefinition();
builder.declaresInnerClass(new InnerClass(factory));
return factory;
}
private void processDependencies(final Collection<MetaClass> types, final DependencyGraphBuilder builder) {
final List<String> problems = new ArrayList<>();
for (final MetaClass type : types) {
processType(type, builder, problems);
}
if (!problems.isEmpty()) {
throw new RuntimeException(buildProblemsMessage(problems));
}
}
private String buildProblemsMessage(final List<String> problems) {
final StringBuilder builder = new StringBuilder();
builder.append("The following problems were found:\n");
for (final String problem : problems) {
builder.append('\t')
.append(problem)
.append('\n');
}
return builder.toString();
}
private void processType(final MetaClass type, final DependencyGraphBuilder builder, final List<String> problems) {
try {
if (isTypeAccessible(type)) {
if (type.isConcrete()) {
final boolean enabled;
if (isSimpleton(type)) {
builder.addInjectable(type, qualFactory.forSource(type), ANY, Dependent.class, InjectableType.Type,
WiringElementType.DependentBean, WiringElementType.Simpleton);
maybeProcessAsStaticOnlyProducer(builder, type, problems);
}
else if ((enabled = isEnabled(type)) && isConstructable(type, problems)) {
final Class<? extends Annotation> directScope = getScope(type);
final Injectable typeInjectable = builder.addInjectable(type, qualFactory.forSource(type), getPathPredicate(type, problems),
directScope, InjectableType.Type, getWiringTypes(type, directScope));
processInjectionPoints(typeInjectable, builder, problems);
maybeProcessAsProducer(builder, type, typeInjectable, true, problems);
maybeProcessAsProvider(typeInjectable, builder, true);
}
else if (!enabled) {
final Class<? extends Annotation> directScope = getScope(type);
final Injectable typeInjectable = builder.addInjectable(type, qualFactory.forSource(type), getPathPredicate(type, problems),
directScope, InjectableType.Disabled, getWiringTypes(type, directScope));
maybeProcessAsProducer(builder, type, typeInjectable, false, problems);
maybeProcessAsProvider(typeInjectable, builder, false);
}
else {
maybeProcessAsStaticOnlyProducer(builder, type, problems);
}
}
else {
maybeProcessAsStaticOnlyProducer(builder, type, problems);
}
if (isPublishableJsType(type)) {
final WiringElementType scopeWiringType = (type.isAnnotationPresent(SharedSingleton.class)
? WiringElementType.SharedSingleton : WiringElementType.DependentBean);
builder.addInjectable(type, qualFactory.forUniversallyQualified(), ANY, Dependent.class, InjectableType.JsType, scopeWiringType);
}
}
} catch (final Throwable t) {
throw new RuntimeException("A fatal error occurred while processing " + type.getFullyQualifiedName(), t);
}
}
private Predicate<List<InjectableHandle>> getPathPredicate(final HasAnnotations annotated, final List<String> problems) {
if (annotated.isAnnotationPresent(Typed.class)) {
final Class<?>[] beanTypes = annotated.getAnnotation(Typed.class).value();
validateAssignableTypes(annotated, beanTypes, problems);
return path -> Object.class.getName().equals(path.get(0)) || Arrays.stream(beanTypes)
.anyMatch(beanType -> path.get(0).getType().getFullyQualifiedName().equals(beanType.getName()));
}
else {
return ANY;
}
}
private void validateAssignableTypes(final HasAnnotations annotated, final Class<?>[] beanTypes, final List<String> problems) {
MetaClass actualRawType;
if (annotated instanceof MetaClass) {
actualRawType = ((MetaClass) annotated).getErased();
}
else if (annotated instanceof MetaField) {
actualRawType = ((MetaField) annotated).getType().getErased();
}
else if (annotated instanceof MetaMethod) {
actualRawType = ((MetaMethod) annotated).getReturnType().getErased();
}
else {
throw new IllegalArgumentException("Unrecognized element kind annotated with @Typed: " + annotated);
}
final Set<String> assignableTypeNames =
actualRawType
.getAllSuperTypesAndInterfaces()
.stream()
.map(type -> type.getFullyQualifiedName())
.collect(Collectors.toSet());
final Optional<String> unassignableTypes =
Arrays
.stream(beanTypes)
.map(Class::getName)
.filter(name -> !assignableTypeNames.contains(name))
.reduce((s1, s2) -> s1 + "\n" + s2);
unassignableTypes.ifPresent(typeNameString -> problems.add(
String.format("The @Typed declaration on [%s] contained the following types not assignable to [%s]:\n%s",
annotated, actualRawType, typeNameString)));
}
private void maybeProcessAsStaticOnlyProducer(final DependencyGraphBuilder builder, final MetaClass type, final List<String> problems) {
maybeProcessAsProducer(builder, type, null, true, problems);
}
private boolean isPublishableJsType(final MetaClass type) {
final JsType jsType = type.getAnnotation(JsType.class);
return jsType != null && !jsType.isNative();
}
private boolean isTypeAccessible(final MetaClass type) {
return type.isPublic() && (isTopLevel(type) || (type.isStatic() && isEnclosingTypeAccessible(type)));
}
/**
* @param typeInjectable If null, only static members will be processed.
*/
private void maybeProcessAsProducer(final DependencyGraphBuilder builder, final MetaClass producerType,
final Injectable typeInjectable, final boolean enabled, final List<String> problems) {
// TODO log error/warning for unused @Disposes methods?
final Collection<MetaMethod> disposesMethods = getAllDisposesMethods(producerType, (typeInjectable == null));
processProducerMethods(typeInjectable, producerType, builder, disposesMethods, enabled, problems);
processProducerFields(typeInjectable, producerType, builder, disposesMethods, enabled, problems);
}
private boolean isTopLevel(final MetaClass type) {
boolean isTopLevel;
// Workaround for http://bugs.java.com/view_bug.do?bug_id=2210448
try {
isTopLevel = (type.asClass() == null || type.asClass().getDeclaringClass() == null);
} catch (final IncompatibleClassChangeError ex) {
isTopLevel = false;
}
return isTopLevel;
}
private boolean isEnclosingTypeAccessible(final MetaClass type) {
boolean hasEnclosingClass = false;
Class<?> enclosing;
// Workaround for http://bugs.java.com/view_bug.do?bug_id=2210448
try {
enclosing = (type.asClass() == null ? null : type.asClass().getDeclaringClass());
hasEnclosingClass = true;
} catch (final IncompatibleClassChangeError ex) {
enclosing = null;
hasEnclosingClass = true;
}
// Assume that the enclosing class is inaccessible if we can't access it because of an IncomaptibleClassChangeError
return !hasEnclosingClass || (enclosing != null && isTypeAccessible(MetaClassFactory.get(enclosing)));
}
private boolean isSimpleton(final MetaClass type) {
for (final Annotation anno : type.getAnnotations()) {
if (nonSimpletonTypeAnnotations.contains(anno.annotationType())
|| isStereotype(anno)
|| isNonNativeJsTypeAnnotation(anno)) {
return false;
}
}
if (!getInjectableConstructors(type).isEmpty()) {
return false;
}
final Collection<Class<? extends Annotation>> producerAnnos = injectionContext.getAnnotationsForElementType(WiringElementType.ProducerElement);
for (final Class<? extends Annotation> producerAnnoType : producerAnnos) {
final List<MetaMethod> producerMethods = type.getMethodsAnnotatedWith(producerAnnoType);
if (!producerMethods.isEmpty() && producerMethods.stream().anyMatch(method -> !method.isStatic())) {
return false;
}
final List<MetaField> producerFields = type.getFieldsAnnotatedWith(producerAnnoType);
if (!producerFields.isEmpty() && producerFields.stream().anyMatch(field -> !field.isStatic())) {
return false;
}
}
if (!type.getMethodsAnnotatedWith(PostConstruct.class).isEmpty()) {
return false;
}
final Collection<Class<? extends Annotation>> injectAnnos = injectionContext.getAnnotationsForElementType(WiringElementType.InjectionPoint);
for (final Class<? extends Annotation> anno : injectAnnos) {
if (!type.getFieldsAnnotatedWith(anno).isEmpty()) {
return false;
}
}
final MetaConstructor noArgConstructor = type.getDeclaredConstructor(new MetaClass[0]);
return noArgConstructor != null && (noArgConstructor.isPublic() || !isJavaScriptObject(type));
}
private boolean isNonNativeJsTypeAnnotation(final Annotation anno) {
return JsType.class.equals(anno.annotationType()) && !((JsType) anno).isNative();
}
private boolean isStereotype(final Annotation anno) {
return anno.annotationType().isAnnotationPresent(Stereotype.class);
}
private WiringElementType[] getWiringTypes(final MetaClass type, final Class<? extends Annotation> directScope) {
final List<WiringElementType> wiringTypes = new ArrayList<>();
wiringTypes.addAll(getWiringTypesForScopeAnnotation(directScope));
if (type.isAnnotationPresent(Alternative.class)) {
wiringTypes.add(WiringElementType.AlternativeBean);
}
if (isPublishableJsType(type)) {
wiringTypes.add(WiringElementType.JsType);
}
if (type.isAnnotationPresent(SharedSingleton.class)) {
wiringTypes.add(WiringElementType.SharedSingleton);
}
if (type.isAnnotationPresent(Specializes.class)) {
wiringTypes.add(WiringElementType.Specialization);
}
if (type.isAnnotationPresent(LoadAsync.class)) {
wiringTypes.add(WiringElementType.LoadAsync);
}
return wiringTypes.toArray(new WiringElementType[wiringTypes.size()]);
}
private void maybeProcessAsProvider(final Injectable typeInjectable, final DependencyGraphBuilder builder,
final boolean enabled) {
final MetaClass type = typeInjectable.getInjectedType();
final Collection<Class<? extends Annotation>> providerAnnotations = injectionContext
.getAnnotationsForElementType(WiringElementType.Provider);
for (final Class<? extends Annotation> anno : providerAnnotations) {
if (type.isAnnotationPresent(anno)) {
if (type.isAssignableTo(Provider.class)) {
addProviderInjectable(typeInjectable, builder, enabled);
}
else if (type.isAssignableTo(ContextualTypeProvider.class)) {
addContextualProviderInjectable(typeInjectable, builder, enabled);
}
break;
}
}
}
private void addContextualProviderInjectable(final Injectable providerInjectable,
final DependencyGraphBuilder builder, final boolean enabled) {
final MetaClass providerImpl = providerInjectable.getInjectedType();
final MetaMethod providerMethod = providerImpl.getMethod("provide", Class[].class, Annotation[].class);
// Do not get generic return type for contextual providers
final MetaClass providedType = providerMethod.getReturnType();
final InjectableType injectableType = (enabled ? InjectableType.ContextualProvider : InjectableType.Disabled);
final Injectable providedInjectable = builder.addInjectable(providedType,
qualFactory.forUniversallyQualified(), EXACT_TYPE, Dependent.class, injectableType,
WiringElementType.Provider, WiringElementType.DependentBean);
builder.addProducerMemberDependency(providedInjectable, providerImpl, providerInjectable.getQualifier(), providerMethod);
}
private void addProviderInjectable(final Injectable providerImplInjectable, final DependencyGraphBuilder builder,
final boolean enabled) {
final MetaClass providerImpl = providerImplInjectable.getInjectedType();
final MetaMethod providerMethod = providerImpl.getMethod("get", new Class[0]);
final MetaClass providedType = getMethodReturnType(providerMethod);
final InjectableType injectableType = (enabled ? InjectableType.Provider : InjectableType.Disabled);
final Injectable providedInjectable = builder.addInjectable(providedType, qualFactory.forSource(providerMethod),
EXACT_TYPE, Dependent.class, injectableType, WiringElementType.Provider, WiringElementType.DependentBean);
builder.addProducerMemberDependency(providedInjectable, providerImplInjectable.getInjectedType(), providerImplInjectable.getQualifier(), providerMethod);
}
private Class<? extends Annotation> getScope(final HasAnnotations annotated) {
final Class<? extends Annotation> foundScope = getDirectScope(annotated);
return (foundScope != null && !injectionContext.isElementType(WiringElementType.DependentBean, foundScope))
? foundScope : Dependent.class;
}
private Class<? extends Annotation> getDirectScope(final HasAnnotations annotated) {
// TODO validate that there's only one scope?
final Set<Class<? extends Annotation>> scopeAnnoTypes = new HashSet<>();
scopeAnnoTypes.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.DependentBean));
scopeAnnoTypes.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.NormalScopedBean));
scopeAnnoTypes.addAll(injectionContext.getAnnotationsForElementType(WiringElementType.PseudoScopedBean));
final Predicate<Class<? extends Annotation>> isExplicitScope =
type -> Arrays.stream(type.getAnnotations())
.map(a -> a.annotationType())
.filter(aType -> NormalScope.class.equals(aType) || Scope.class.equals(aType))
.findAny()
.isPresent();
final PriorityQueue<Class<? extends Annotation>> pq = new PriorityQueue<>((o1, o2) -> {
final int score1, score2;
score1 = (isExplicitScope.test(o1) ? 1 : -1);
score2 = (isExplicitScope.test(o2) ? 1 : -1);
return score2 - score1;
});
for (final Annotation anno : annotated.getAnnotations()) {
final Class<? extends Annotation> annoType = anno.annotationType();
if (scopeAnnoTypes.contains(annoType)) {
pq.add(annoType);
} else if (annoType.isAnnotationPresent(Stereotype.class)) {
final Class<? extends Annotation> stereotypeScope = getDirectScope(MetaClassFactory.get(annoType));
if (stereotypeScope != null) {
pq.add(stereotypeScope);
}
}
}
if (pq.isEmpty()) {
return null;
}
else {
return pq.poll();
}
}
/**
* @param producerTypeInjectable If this parameter is null, only static methods will be processed.
*/
private void processProducerMethods(final Injectable producerTypeInjectable, final MetaClass producerType,
final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods, final boolean enabled, final List<String> problems) {
final boolean staticOnly = (producerTypeInjectable == null);
final Collection<Class<? extends Annotation>> producerAnnos = injectionContext.getAnnotationsForElementType(WiringElementType.ProducerElement);
for (final Class<? extends Annotation> anno : producerAnnos) {
final List<MetaMethod> methods = producerType.getDeclaredMethodsAnnotatedWith(anno);
for (final MetaMethod method : methods) {
if (!staticOnly || method.isStatic()) {
processProducerMethod(producerTypeInjectable, producerType, builder, disposesMethods, method, enabled, problems);
}
}
}
}
private void processProducerMethod(final Injectable producerTypeInjectable, final MetaClass producerType,
final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods, final MetaMethod method,
final boolean enabled, final List<String> problems) {
final Class<? extends Annotation> directScope = getScope(method);
final WiringElementType[] wiringTypes = getWiringTypeForProducer(producerType, method, directScope);
final InjectableType injectableType = (enabled ? InjectableType.Producer : InjectableType.Disabled);
final Injectable producedInjectable = builder.addInjectable(getMethodReturnType(method),
qualFactory.forSource(method), getPathPredicate(method, problems), directScope, injectableType, wiringTypes);
if (method.isStatic()) {
builder.addProducerMemberDependency(producedInjectable, producerType, method);
}
else {
builder.addProducerMemberDependency(producedInjectable, producerType, producerTypeInjectable.getQualifier(), method);
}
if (enabled) {
processProducerAndDisposerMethodsDependencies(builder, disposesMethods, method, producedInjectable);
}
}
private MetaClass getMethodReturnType(final MetaMethod method) {
final MetaType genericType = method.getGenericReturnType();
return getMetaClassFromGeneric(genericType).orElseGet(method::getReturnType);
}
private static Optional<MetaClass> getMetaClassFromGeneric(final MetaType genericType) {
if (genericType instanceof MetaClass) {
return Optional.of((MetaClass) genericType);
}
else if (genericType instanceof MetaParameterizedType && ((MetaParameterizedType) genericType).getRawType() instanceof MetaClass) {
final MetaParameterizedType mpt = (MetaParameterizedType) genericType;
@SuppressWarnings({ "unchecked", "rawtypes" })
final MetaType[] typeArgs = Arrays
.stream(mpt.getTypeParameters())
.map(arg -> ((Optional<MetaType>) (Optional) getMetaClassFromGeneric(arg)).orElse(arg))
.toArray(MetaType[]::new);
return Optional.of(parameterizedAs((MetaClass) mpt.getRawType(), typeParametersOf(typeArgs)));
}
else {
return Optional.empty();
}
}
private void processProducerAndDisposerMethodsDependencies(final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods,
final MetaMethod method, final Injectable producedInjectable) {
final MetaParameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
final MetaParameter param = params[i];
builder.addProducerParamDependency(producedInjectable, param.getType(), qualFactory.forSink(param), i, param);
}
final Collection<MetaMethod> matchingDisposes = getMatchingMethods(method, disposesMethods);
if (matchingDisposes.size() > 1) {
// TODO descriptive message with names of disposers found.
throw new RuntimeException();
} else if (!matchingDisposes.isEmpty()) {
addDisposerDependencies(producedInjectable, matchingDisposes.iterator().next(), builder);
}
}
private WiringElementType[] getWiringTypeForProducer(final MetaClass enclosingClass, final HasAnnotations annotated,
final Class<? extends Annotation> directScope) {
final List<WiringElementType> wiringTypes = new ArrayList<>();
wiringTypes.addAll(getWiringTypesForScopeAnnotation(directScope));
if (annotated.isAnnotationPresent(Specializes.class)) {
wiringTypes.add(WiringElementType.Specialization);
}
if (enclosingClass.isAnnotationPresent(LoadAsync.class)) {
wiringTypes.add(WiringElementType.LoadAsync);
}
return wiringTypes.toArray(new WiringElementType[wiringTypes.size()]);
}
private Collection<MetaMethod> getAllDisposesMethods(final MetaClass type, final boolean staticOnly) {
final Collection<MetaMethod> disposers = new ArrayList<>();
for (final MetaMethod method : type.getMethods()) {
if (staticOnly && !method.isStatic()) {
continue;
}
final List<MetaParameter> disposerParams = method.getParametersAnnotatedWith(Disposes.class);
if (disposerParams.size() > 1) {
throw new RuntimeException("Found method " + method + " in " + method.getDeclaringClassName()
+ " with multiple @Disposes parameters.");
} else if (disposerParams.size() == 1) {
disposers.add(method);
}
}
return disposers;
}
private Collection<MetaMethod> getMatchingMethods(final MetaClassMember member, final Collection<MetaMethod> disposesMethods) {
final Collection<MetaMethod> matching = new ArrayList<>();
final Qualifier memberQual = qualFactory.forSource(member);
final MetaClass producedType = getProducedType(member);
for (final MetaMethod candidate : disposesMethods) {
final MetaParameter disposesParam = candidate.getParametersAnnotatedWith(Disposes.class).iterator().next();
if (producedType.isAssignableTo(disposesParam.getType())) {
final Qualifier paramQual = qualFactory.forSink(disposesParam);
if (paramQual.isSatisfiedBy(memberQual)) {
matching.add(candidate);
}
}
}
return matching;
}
private MetaClass getProducedType(final MetaClassMember member) {
if (member instanceof MetaField) {
return ((MetaField) member).getType();
} else if (member instanceof MetaMethod) {
return ((MetaMethod) member).getReturnType();
} else {
throw new RuntimeException("Producer members must be fields or methods, but found " + member);
}
}
private void addDisposerDependencies(final Injectable producedInjectable, final MetaMethod disposer, final DependencyGraphBuilder builder) {
for (final MetaParameter param : disposer.getParameters()) {
if (param.isAnnotationPresent(Disposes.class)) {
builder.addDisposesMethodDependency(producedInjectable, disposer.getDeclaringClass(), qualFactory.forSink(disposer.getDeclaringClass()), disposer);
} else {
builder.addDisposesParamDependency(producedInjectable, param.getType(), qualFactory.forSink(param), param.getIndex(), param);
}
}
}
private Collection<WiringElementType> getWiringTypesForScopeAnnotation(final Class<? extends Annotation> directScope) {
if (injectionContext.isElementType(WiringElementType.NormalScopedBean, directScope)) {
return Collections.singleton(WiringElementType.NormalScopedBean);
} else if (injectionContext.isElementType(WiringElementType.DependentBean, directScope)) {
return Arrays.asList(WiringElementType.DependentBean, WiringElementType.PseudoScopedBean);
} else {
return Collections.singleton(WiringElementType.PseudoScopedBean);
}
}
/**
* @param producerInjectable If null then only static fields will be processed.
*/
private void processProducerFields(final Injectable producerInjectable, final MetaClass producerType,
final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods, final boolean enabled,
final List<String> problems) {
final boolean staticOnly = (producerInjectable == null);
final Collection<Class<? extends Annotation>> producerAnnos = injectionContext.getAnnotationsForElementType(WiringElementType.ProducerElement);
for (final Class<? extends Annotation> producerAnno : producerAnnos) {
final List<MetaField> fields = producerType.getFieldsAnnotatedWith(producerAnno);
for (final MetaField field : fields) {
if (!staticOnly || field.isStatic()) {
processProducerField(producerInjectable, producerType, builder, disposesMethods, field, enabled, problems);
}
}
}
}
private void processProducerField(final Injectable producerInjectable, final MetaClass producerType,
final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods, final MetaField field,
final boolean enabled, final List<String> problems) {
final Class<? extends Annotation> scopeAnno = getScope(field);
final InjectableType injectableType = (enabled ? InjectableType.Producer : InjectableType.Disabled);
final Injectable producedInjectable = builder.addInjectable(field.getType(), qualFactory.forSource(field), getPathPredicate(field, problems),
scopeAnno, injectableType, getWiringTypeForProducer(producerType, field, scopeAnno));
if (field.isStatic()) {
builder.addProducerMemberDependency(producedInjectable, producerType, field);
}
else {
builder.addProducerMemberDependency(producedInjectable, producerInjectable.getInjectedType(), producerInjectable.getQualifier(), field);
}
if (enabled) {
processDisposerDependencies(builder, disposesMethods, field, producedInjectable);
}
}
private void processDisposerDependencies(final DependencyGraphBuilder builder, final Collection<MetaMethod> disposesMethods,
final MetaField field, final Injectable producedInjectable) {
final Collection<MetaMethod> matchingDisposers = getMatchingMethods(field, disposesMethods);
if (matchingDisposers.size() > 1) {
// TODO add descriptive error message.
throw new RuntimeException();
} else if (!matchingDisposers.isEmpty()) {
addDisposerDependencies(producedInjectable, matchingDisposers.iterator().next(), builder);
}
}
private void processInjectionPoints(final Injectable typeInjectable, final DependencyGraphBuilder builder, final List<String> problems) {
final MetaClass type = typeInjectable.getInjectedType();
final MetaConstructor injectableConstructor = getInjectableConstructor(type);
if (injectableConstructor != null) {
if (!injectableConstructor.isPublic()) {
problems.add("The constructor of " + typeInjectable.getInjectedType().getFullyQualifiedName() + " annotated with @Inject must be public.");
}
addConstructorInjectionPoints(typeInjectable, injectableConstructor, builder);
}
addFieldInjectionPoints(typeInjectable, builder, problems);
addMethodInjectionPoints(typeInjectable, builder, problems);
}
private void addMethodInjectionPoints(final Injectable typeInjectable, final DependencyGraphBuilder builder, final List<String> problems) {
final MetaClass type = typeInjectable.getInjectedType();
final Collection<Class<? extends Annotation>> injectAnnotations = injectionContext.getAnnotationsForElementType(WiringElementType.InjectionPoint);
for (final Class<? extends Annotation> inject : injectAnnotations) {
for (final MetaMethod setter : type.getMethodsAnnotatedWith(inject)) {
if (setter.getParameters().length != 1) {
problems.add("The method injection point " + setter.getName() + " in "
+ setter.getDeclaringClass().getFullyQualifiedName() + " should have exactly one parameter, not "
+ setter.getParameters().length + ".");
} else {
final MetaParameter metaParam = setter.getParameters()[0];
builder.addSetterMethodDependency(typeInjectable, metaParam.getType(), qualFactory.forSink(setter.getParameters()[0]), setter);
}
}
}
}
private void addFieldInjectionPoints(final Injectable typeInjectable, final DependencyGraphBuilder builder, final List<String> problems) {
final boolean noPublicFieldsAllowed = typeInjectable.getWiringElementTypes().contains(WiringElementType.NormalScopedBean);
final MetaClass type = typeInjectable.getInjectedType();
final Collection<Class<? extends Annotation>> injectAnnotations = injectionContext.getAnnotationsForElementType(WiringElementType.InjectionPoint);
for (final Class<? extends Annotation> inject : injectAnnotations) {
for (final MetaField field : type.getFieldsAnnotatedWith(inject)) {
if (noPublicFieldsAllowed && field.isPublic()) {
problems.add("The normal scoped bean " + type.getFullyQualifiedName() + " has a public field " + field.getName());
}
builder.addFieldDependency(typeInjectable, field.getType(), qualFactory.forSink(field), field);
}
}
}
private void addConstructorInjectionPoints(final Injectable concreteInjectable, final MetaConstructor injectableConstructor, final DependencyGraphBuilder builder) {
final MetaParameter[] params = injectableConstructor.getParameters();
for (int i = 0; i < params.length; i++) {
final MetaParameter param = params[i];
builder.addConstructorDependency(concreteInjectable, param.getType(), qualFactory.forSink(param), i, param);
}
}
private MetaConstructor getInjectableConstructor(final MetaClass type) {
final Collection<Class<? extends Annotation>> injectAnnotations = injectionContext.getAnnotationsForElementType(WiringElementType.InjectionPoint);
for (final MetaConstructor con : type.getConstructors()) {
for (final Class<? extends Annotation> anno : injectAnnotations) {
if (con.isAnnotationPresent(anno)) {
return con;
}
}
}
return null;
}
private boolean isConstructable(final MetaClass type, final List<String> problems) {
final boolean explicitlyScoped = getDirectScope(type) != null;
final List<MetaConstructor> injectableConstructors = getInjectableConstructors(type);
final MetaConstructor noArgConstructor = type.getDeclaredConstructor(new MetaClass[0]);
if (injectableConstructors.size() > 1) {
problems.add(type.getFullyQualifiedName() + " has " + injectableConstructors.size() + " constructors annotated with @Inject.");
return false;
}
else {
if (injectableConstructors.size() == 1) {
final MetaConstructor injectConstructor = injectableConstructors.get(0);
final boolean instantiable = injectConstructor.isPublic() || !isJavaScriptObject(type);
if (!instantiable) {
problems.add(String.format("Cannot access constructor for %s.", type.getFullyQualifiedName()));
}
if (scopeDoesNotRequireProxy(type)) {
return instantiable;
} else if (noArgConstructor == null || !(noArgConstructor.isPublic() || noArgConstructor.isProtected())) {
log.debug("The class {} must be proxiable but does not have an accessible no-argument constructor", type.getFullyQualifiedName());
final boolean injectConstructorProxiable = injectConstructor.isPublic() || injectConstructor.isProtected();
if (!injectConstructorProxiable) {
problems.add(String.format(
"The class %s must be proxiable but has no injectable constructor or no-argument constructor accessible to subclasses.",
type.getFullyQualifiedName()));
}
return instantiable && injectConstructorProxiable;
} else {
return instantiable;
}
} else {
final boolean instantiable = noArgConstructor != null && (noArgConstructor.isPublic() || !isJavaScriptObject(type));
final boolean proxiable = noArgConstructor != null && (noArgConstructor.isPublic() || noArgConstructor.isProtected());
final boolean passesProxiability = scopeDoesNotRequireProxy(type) || proxiable;
if (explicitlyScoped) {
if (!instantiable) {
problems.add(String.format("Cannot access constructor for %s.", type.getFullyQualifiedName()));
}
if (!passesProxiability) {
problems.add(String.format(
"%s must be proxiable but does not have a no-argument constructor accessible to subclasses.",
type.getFullyQualifiedName()));
}
}
return instantiable && passesProxiability;
}
}
}
private boolean isJavaScriptObject(final MetaClass type) {
return type.isAssignableTo(JavaScriptObject.class) || isNativeJSType(type);
}
private boolean isNativeJSType(final MetaClass type) {
final JsType anno = type.getAnnotation(JsType.class);
return type.getAnnotation(JsType.class) != null && anno.isNative();
}
private boolean scopeDoesNotRequireProxy(final MetaClass type) {
final Class<? extends Annotation> scope = getScope(type);
return scope.equals(EntryPoint.class) || injectionContext.getAnnotationsForElementType(WiringElementType.DependentBean).contains(scope);
}
private List<MetaConstructor> getInjectableConstructors(final MetaClass type) {
final Collection<Class<? extends Annotation>> injectAnnotations = injectionContext.getAnnotationsForElementType(WiringElementType.InjectionPoint);
final List<MetaConstructor> cons = new ArrayList<>();
for (final MetaConstructor con : type.getConstructors()) {
for (final Class<? extends Annotation> anno : injectAnnotations) {
if (con.isAnnotationPresent(anno)) {
cons.add(con);
}
}
}
return cons;
}
private boolean isEnabled(final MetaClass type) {
final boolean hasEnablingProperty = hasEnablingProperty(type);
return (injectionContext.isWhitelisted(type) && !injectionContext.isBlacklisted(type))
&& ((hasEnablingProperty && isEnabledByProperty(type)) || (!hasEnablingProperty && isActive(type)));
}
private boolean isActive(final MetaClass type) {
if (type.isAnnotationPresent(Alternative.class)) {
return isAlternativeEnabled(type);
} else {
return true;
}
}
private boolean isAlternativeEnabled(final MetaClass type) {
if (alternatives == null) {
final String userDefinedAlternatives = EnvUtil.getEnvironmentConfig().getFrameworkOrSystemProperty("errai.ioc.enabled.alternatives");
if (userDefinedAlternatives != null) {
alternatives = new HashSet<>(Arrays.asList(userDefinedAlternatives.split("\\s+")));
} else {
alternatives = Collections.emptyList();
}
}
return alternatives.contains(type.getFullyQualifiedName());
}
private boolean isEnabledByProperty(final MetaClass type) {
final EnabledByProperty anno = type.getAnnotation(EnabledByProperty.class);
final boolean propValue = getPropertyValue(anno.value());
final boolean negated = anno.negated();
return propValue ^ negated;
}
private boolean getPropertyValue(final String propName) {
final String propertyValue = EnvUtil.getEnvironmentConfig().getFrameworkOrSystemProperty(propName);
if (propertyValue == null) {
return false;
}
try {
return Boolean.parseBoolean(propertyValue);
} catch (final Throwable t) {
throw new RuntimeException("Could not parse " + propName + " value, " + propertyValue + ", to boolean.", t);
}
}
private boolean hasEnablingProperty(final MetaClass type) {
return type.isAnnotationPresent(EnabledByProperty.class);
}
private static boolean exactTypePredicate(final List<InjectableHandle> path) {
final int pathLength = path.size() - 1;
return pathLength == 0 || path.get(0).getType().getFullyQualifiedName().equals(path.get(pathLength).getType().getFullyQualifiedName());
}
}